package fons.model.query.fun;

import java.util.List;

import com.google.common.collect.Lists;

public class ArithmeticFun implements Function {
	private final List<Function> list;
	private final Operator op;

	public ArithmeticFun (Operator op) {
		this(op, Lists.<Function>newArrayList());
	}

	public ArithmeticFun (Operator op, List<Function> list) {
		this.list = list;
		this.op = op;
	}


	@Override
	public Function apply(Function other) {		
		List<Function> newValues = Lists.newLinkedList(this.list);
		Function result = other;//other.apply();

		if (result instanceof ListFun && !(result instanceof ReferenceFun)) {
			newValues.addAll(((ListFun) result).get());
		} else {
			newValues.add(result);
		}

		return new ArithmeticFun(op, newValues);		
	}

	@Override
	public Function apply() {
		Number result = null;

		try {
			List<Function> tmp = Lists.newArrayList();
			for (Function fun: list) {
				if (fun instanceof ReferenceFun) {
					tmp.addAll(((ListFun) ((ReferenceFun) fun).apply()).get());

				} else {
					tmp.add(fun.apply());
				}
			}

			if (tmp.size() == 1) return new ArithmeticFun(op, list);

			for (Function fun: tmp) {
				Number num = Functions.reduceTo(fun, Number.class);
				if (result == null) result = num;
				else {
					result = (Number) new Number(op.apply(result.getValue(), num.getValue()),
							op.apply(result.getMeasurement(), num.getMeasurement()),
							op.apply(result.getDimensionsByRole(), num.getDimensionsByRole()));
				}
			}		
			return result;
		} catch (IllegalStateException e) {
			return this;
		}
	}



	public static void main (String[] args) {
		Function sum = new ArithmeticFun(Operator.SUM);
		Function dif = new ArithmeticFun(Operator.DIFFERENCE);
		Function mul = new ArithmeticFun(Operator.MULTIPLICATION);
		Function div = new ArithmeticFun(Operator.DIVISION);
		Function twice = mul.apply(new Number(2));
		Function list = new ListFun();
		Function some_list = list.apply(new Number(4)).apply(new Number(6));
		Function map = new MapFun();
		Function inc = sum.apply(new Number(1));
		Function foldl = new FoldLeftFun();
		Function first = new FirstFun();
		Function less = new CompareFun(Comparator.LESS);
		Function more = new CompareFun(Comparator.MORE);
		Function sort = new SortFun();
		Function zip = new ZipFun();
		Function filter = new FilterFun();
		Function count = new CountFun();

		System.out.println(sum.apply(new Number(2)).apply(new Number(3)).apply());
		System.out.println(list.apply(new Number(1)).apply(new Number(2)).apply());
		System.out.println(sum.apply(some_list).apply());

		Function something = list.apply(sum.apply(some_list)).apply(dif.apply(some_list)).apply(some_list);
		System.out.println(something);

		System.out.println(map.apply(inc).apply(some_list)
				.apply());

		System.out.println(foldl.apply(sum).apply(new Number(0)).apply(map.apply(inc).apply(some_list))
				.apply());

		System.out.println(twice.apply(twice.apply(new Number(5))).apply());

		System.out.println("Sorted: " +
				sort.apply(more).apply(map.apply(twice).apply(some_list.apply(new Number(3)).apply(new Number(1))))
				.apply());

		System.out.println("Zip: " +
				zip.apply(sum).apply(some_list.apply(new Number(4))).apply(some_list)
				.apply());

		Function long_list_value = new ListFun().apply(new Number(1)).apply(new Number(2)).apply(new Number(3))
				.apply(new Number(4)).apply(new Number(5)).apply(new Number(6));


		ReferenceFun long_list = new ReferenceFun("long_list");
		ReferenceFun sort_ref = new ReferenceFun("sort");
		ReferenceFun first_ref = new ReferenceFun("first");
		ReferenceFun more_ref = new ReferenceFun("more");
		ReferenceFun less_ref = new ReferenceFun("less");
		ReferenceFun zip_ref = new ReferenceFun("zip");
		ReferenceFun sum_ref = new ReferenceFun("sum");
		ReferenceFun mul_ref = new ReferenceFun("mul");
		ReferenceFun div_ref = new ReferenceFun("div");
		ReferenceFun count_ref = new ReferenceFun("count");
		ReferenceFun filter_ref = new ReferenceFun("filter");
		ReferenceFun map_ref = new ReferenceFun("map");

		Function lazy = map_ref.apply(mul_ref.apply(new Number(3))).apply(zip_ref.apply(sum_ref).apply(long_list).apply(long_list));
		Scope scope = new Scope();

		Function lazy_filter =
				sort.apply(less).apply(
						filter.apply(
								less.apply(
										div.apply(sum.apply(long_list)).apply(
												  count.apply(long_list))))
							  .apply(long_list));

		Function lazy_most =
				first.apply(sort.apply(more).apply(long_list));

		scope.set("long_list", (ListFun) long_list_value);
		scope.set("sort", sort);
		scope.set("first", first);
		scope.set("more", more);
		scope.set("zip", zip);
		scope.set("sum", sum);
		scope.set("map", map);
		scope.set("div", div);
		scope.set("mul", mul);
		scope.set("count", count);
		scope.set("filter", filter);
		scope.set("less", less);

		long_list.setScope(scope);
		sort_ref.setScope(scope);
		first_ref.setScope(scope);
		more_ref.setScope(scope);
		less_ref.setScope(scope);
		zip_ref.setScope(scope);
		sum_ref.setScope(scope);
		map_ref.setScope(scope);
		mul_ref.setScope(scope);
		div_ref.setScope(scope);
		count_ref.setScope(scope);
		filter_ref.setScope(scope);

		System.out.println("Lazy operation: " + lazy.apply());

		System.out.println("List: " + long_list_value.apply());
		System.out.println("Average: " + div.apply(sum.apply(long_list_value)).apply(count.apply(long_list_value))
				.apply());
		System.out.println("filter > average: " +
				filter
				.apply(less.apply(
						div.apply(sum.apply(long_list_value))
						.apply(count.apply(long_list_value))))
						.apply(long_list_value)

						.apply());


		System.out.println("Lazy filter > average: " + lazy_filter.apply());
		System.out.println("Lazy most: " + lazy_most.apply());

	}

	@Override
	public String toString() {
		return "ArithmeticFun [list=" + list + ", op=" + op + "]";
	}
}
